﻿// -----------------------------------------------------------------------
// <copyright file="RulesChecker.cs" company="">
// TODO: Update copyright text.
// </copyright>
// -----------------------------------------------------------------------

namespace TestProject.FunWithExpressions
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    using System.Collections;
    using System.Reflection;

    internal class RulesChecker : ExpressionVisitor
    {
        private readonly object data;
        private List<ParameterExpression> parameterExpressionList;
        private bool hasConfidentielle = false;
        private bool hasNonDiffusable = false;
        private bool hasDivisionParZero = false;
        private bool hasManquante = false;

        public RulesChecker(object data)
        {
            this.parameterExpressionList = new List<ParameterExpression>();
            this.data = data;
        }

        public static RulesCheckerResult Check(object data, Expression expression)
        {

            return GetResults(data, expression);


            if (IsIEnumerable(data))
            {
                var result = new RulesCheckerResult(false, false, false, false);

                IEnumerable dataItems = (IEnumerable)data;

                foreach (object dataItem in dataItems)
                {
                    //result = MergeResults(result, Check(dataItem, expression));
                    //result = MergeResults(result, GetResults(dataItem, expression));
                }

                return result;

            }
            else
            {
                return GetResults(data, expression);
            }
        }

        private static RulesCheckerResult MergeResults(RulesCheckerResult results1, RulesCheckerResult results2)
        {
            var hasConfidentielle = results1.HasConfidentielle || results2.HasConfidentielle;
            var hasDivisionParZero = results1.HasDivisionParZero || results2.HasDivisionParZero;
            var hasManquante = results1.HasManquante || results2.HasManquante;
            var hasNonDiffusable = results1.HasNonDiffusable || results2.HasNonDiffusable;

            return new RulesCheckerResult(hasConfidentielle, hasNonDiffusable, hasDivisionParZero, hasManquante);
        }

        private static RulesCheckerResult GetResults(object data, Expression expression)
        {
            var visitor = new RulesChecker(data);
            visitor.Visit(expression);

            return new RulesCheckerResult(
                visitor.hasConfidentielle,
                visitor.hasNonDiffusable,
                visitor.hasDivisionParZero,
                visitor.hasManquante);
        }

        private static bool IsIEnumerable(object source)
        {
            if (source == null)
            {
                return false;
            }

            var type = source.GetType();

            return type.IsGenericType && type.GetInterface("IEnumerable") != null;
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            var returnValue = base.VisitBinary(node);
            if (!this.hasDivisionParZero && node.NodeType == ExpressionType.Divide)
            {
                if (node.Right.NodeType == ExpressionType.MemberAccess)
                {
                    var rightMemeberExpression = (MemberExpression)node.Right;
                    var propertyInfo = (PropertyInfo)rightMemeberExpression.Member;
                    var value = Convert.ToInt32(propertyInfo.GetValue(this.data, null));

                    this.hasDivisionParZero = value == 0;
                }
                else if (node.Right.NodeType == ExpressionType.Constant)
                {
                    var rightConstantExpression = (ConstantExpression)node.Right;
                    var value = Convert.ToInt32(rightConstantExpression.Value);

                    this.hasDivisionParZero = value == 0;
                }
                else
                {
                    Expression rightExpression = node.Right;
                    if (!this.hasDivisionParZero)
                    {
                        int value = Convert.ToInt32(Execute(rightExpression));
                        this.hasDivisionParZero = value == 0;
                    }
                }

            }

            return returnValue;
        }

        private object Execute(Expression node)
        {
            LambdaExpression lambda;
            if (node.NodeType == ExpressionType.Call)
            {
                var callNode = (MethodCallExpression)node;
                lambda = callNode.Arguments[1] as LambdaExpression;
                var lambaDelegate = lambda.Compile();
                var value = callNode.Method.Invoke(null, new object[] { this.data, lambaDelegate });
                return value;
                //return Expression.Constant(value);
            }
            if (parameterExpressionList != null)
            {
                lambda = Expression.Lambda(node, parameterExpressionList);
                var compiled = lambda.Compile();
                return compiled.DynamicInvoke(this.data);
            }
            else
            {
                return node;
            }
        }

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            foreach (ParameterExpression exp in node.Parameters)
            {
                if (!this.parameterExpressionList.Contains(exp))
                {
                    this.parameterExpressionList.Add(exp);
                }
            }
            return base.VisitLambda<T>(node);
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            var returnValue = base.VisitParameter(node);

            if (!this.parameterExpressionList.Contains(node))
            {
                this.parameterExpressionList.Add(node);
            }

            return returnValue;
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            this.CheckValue(this.ConvertToNullableInt(node.Value));

            return base.VisitConstant(node);
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (!this.hasConfidentielle || !this.hasNonDiffusable || !this.hasManquante)
            {
                var propertyInfo = node.Member as PropertyInfo;

                if (propertyInfo != null)
                {
                    var value = propertyInfo.GetValue(this.data, null);

                    this.CheckValue(this.ConvertToNullableInt(value));
                }
            }

            return base.VisitMember(node);
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Method.DeclaringType == typeof(Enumerable) && node.Arguments.Count == 2)
            {

                switch (node.Method.Name)
                {
                    case "Count":
                    case "Min":
                    case "Max":
                    case "Sum":
                    case "Average":
                        var lambda = node.Arguments[1] as LambdaExpression;
                        var lambaDelegate = lambda.Compile();
                        var enumerableObject = this.data as IEnumerable<object>;
                        if (enumerableObject != null)
                        {
                            foreach (object item in enumerableObject)
                            {
                                var p = new RulesChecker(item);
                                p.Visit(lambda);
                                this.hasConfidentielle = this.hasConfidentielle || p.hasConfidentielle;
                                this.hasDivisionParZero = this.hasDivisionParZero || p.hasDivisionParZero;
                                this.hasManquante = this.hasManquante || p.hasManquante;
                                this.hasNonDiffusable = this.hasNonDiffusable || p.hasNonDiffusable;
                            }
                            return node;
                        }
                        break;
                }
            }

            return base.VisitMethodCall(node);
        }

        private void CheckValue(int? value)
        {
            if (!this.hasManquante)
            {
                this.hasManquante = value == TypeDonnee.Manquante.BdValue;
            }

            if (!this.hasConfidentielle)
            {
                this.hasConfidentielle = value == TypeDonnee.Confidentielle.BdValue;
            }

            if (!this.hasNonDiffusable)
            {
                this.hasNonDiffusable = value == TypeDonnee.NonDiffusable.BdValue;
            }
        }

        private int? ConvertToNullableInt(object value)
        {
            if (!value.GetType().IsPrimitive)
            {
                return int.MinValue;
            }

            // MTO.Framework.Common.Convert.To<int?>(value);
            return (int?)value;
        }
    }
}
